<?php

/*

    MMetaverse - Open Metaverse Access Library

    Copyright (c) 2009 Mary Jane Soft - Marlon J. Manrique

    http://www.maryjanesoft.com
    http://www.marlonj.com

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General
    Public License along with this library; if not, write to the
    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
    Boston, MA  02111-1307  USA

    $Id$

*/

/**
 * @package mjs.mmetaverse.packet
 */

/**
 * Network Packet
 */
class MPacket {

    /**
     * Low Frequency Packet
     *
     * @var integer
     */
    const FREQUENCY_LOW = 1;

    /**
     * Medium Frequency Packet
     *
     * @var integer
     */
    const FREQUENCY_MEDIUM = 2;

    /**
     * High Frequency Packet
     *
     * @var integer
     */
    const FREQUENCY_HIGH = 3;

    /**
     * Fixed Frequency Packet
     *
     * @var integer
     */
    const FREQUENCY_FIXED = 4;

    /**
     * Packet Frequency
     *
     * @var integer
     */
    private $frequency;

    /**
     * Packet data
     *
     * @var array
     */
    private $data;

    /**
     * Current data position
     *
     * @var integer
     */
    private $dataPos;

    /**
     * Number of the message
     *
     * @var integer
     */
    private $messageNumber;

    /**
     * Create a Packet
     *
     * @param integer $messageNumber
     * @param integer $frequency Frequency of the message
     * @param array $data Message data 
     */
    public function MPacket($messageNumber = -1, 
        $frequency = -1, $data = array()) {
        
        // Init data        
        $this->data = $data;
        $this->dataPos = 0;
        $this->messageNumber = $messageNumber;
        $this->frequency = $frequency;

        // If not data packet given
        // Calculate values 
        if($messageNumber == -1 && $frequency == -1) {
            $this->parse();
        } else {
            // Add new header to empty packet 
            $this->addHeader($messageNumber);
        }
    }

    /**
     * Add Header
     *
     * @param integer $messageNumber Message number 
     */
    private function addHeader($messageNumber) {
        // Add flags
        $this->data[$this->dataPos++] = chr(0);

        // Add sequence number
        $this->data[$this->dataPos++] = chr(0);
        $this->data[$this->dataPos++] = chr(0);
        $this->data[$this->dataPos++] = chr(0);
        $this->data[$this->dataPos++] = chr(0);

        // Add aditional information
        $this->data[$this->dataPos++] = chr(0);

        // Add medium identifier 
        if($this->frequency == MPacket::FREQUENCY_LOW ||
            $this->frequency == MPacket::FREQUENCY_FIXED) {
            $this->data[$this->dataPos++] = chr(0xFF);
            $this->data[$this->dataPos++] = chr(0xFF);
        }

        // Add Medium first part 
        if($this->frequency == MPacket::FREQUENCY_MEDIUM ||
            $this->frequency == MPacket::FREQUENCY_LOW) {
            $this->data[$this->dataPos++] = chr(($messageNumber >> 8) & 0xFF);
        } else if($this->frequency == MPacket::FREQUENCY_FIXED) {
            $this->data[$this->dataPos++] = chr(0xFF);
        }

        // Add message number
        $this->data[$this->dataPos++] = chr($messageNumber & 0xFF);
    }

    /**
     * Update sequence number
     *
     * @param integer sequenceNumber
     */
    public function setSequenceNumber($sequenceNumber) {
        // Set sequence number 
        $this->data[1] = chr($sequenceNumber >> 24);
        $this->data[2] = chr($sequenceNumber >> 16);
        $this->data[3] = chr($sequenceNumber >> 8);
        $this->data[4] = chr($sequenceNumber);
    }

    /**
     * Add byte
     *
     * @param byte $value
     */
    public function addU8($value) {

        // Add integer values
        $this->data[$this->dataPos++] = chr($value & 0xFF);
    }

    /**
     * Add an integer to the packet
     *
     * @param integer $value
     */
    public function addU32($value) {

        // Add integer values 
        $this->data[$this->dataPos++] = chr($value);
        $this->data[$this->dataPos++] = chr($value >> 8);
        $this->data[$this->dataPos++] = chr($value >> 16);
        $this->data[$this->dataPos++] = chr($value >> 24);
    }

    /**
     * Add a uuid to the packet
     *
     * @param string $value
     */
    public function addUUID($value) {
        // Remove hyphens
        $value = str_replace('-', '', $value);

        // Get each hexa and convert to decimal 
        for($i=0; $i<16; $i++) {
            $this->data[$this->dataPos++]
                = chr(hexdec(substr($value, $i*2, 2)));
        }
    }

    /**
     * Add Float
     *
     * @param float $value
     */
    public function addF32($value)
    {
        $this->data[$this->dataPos++] = chr($value);
        $this->data[$this->dataPos++] = chr($value >> 8);
        $this->data[$this->dataPos++] = chr($value >> 16);
        $this->data[$this->dataPos++] = chr($value >> 24);
    }

    /**
     * Add Vector3
     * @param MVector3 $value 
     */
    public function addVector3(MVector3 $value)
    {
        // Add coordinates 
        $this->addF32($value->getX());
        $this->addF32($value->getY());
        $this->addF32($value->getZ());
    }

    /**
     * Add Quaternion
     * @param MQuaternion $value
     */
    public function addQuaternion(MQuaternion $value)
    {
        // Add coordinates
        $this->addF32($value->getX());
        $this->addF32($value->getY());
        $this->addF32($value->getZ());
        $this->addF32($value->getS());
    }

    /**
     * Return the data like a single string
     *
     * @return string 
     */
    public function getRowData() {
        return join($this->data);
    }

    /**
     * Return the hexa representation
     *
     * @return string
     */
    public function getHexData() {
        // Create a buffer
        $hexa = '';

        // Convert each value in hexa
        foreach($this->data as $value) {
            $hexa .= dechex(ord($value)) . ' ';
        }

        // Return hexa 
        return $hexa;
    }

    /**
     * Return the frequency of the packet
     *
     * @param string $data
     * @return integer Packet frequency
     */
    private function calculateFrequency()
    {
        // 0x?? Number is first byte
        if($this->data[1] != 0xFF) {
            $this->frequency = MPacket::FREQUENCY_HIGH;
            return;
        }

        // 0xFF?? Medium
        if($this->data[2] != 0xFF) {
            $this->frequency = MPacket::FREQUENCY_MEDIUM;
            return;
        }

        // 0xFFFF???? Medium
        if($this->data[3] != 0xFF) {
            $this->frequency = MPacket::FREQUENCY_LOW;
            return;
        }

        // Fixed
        $this->frequency = MPacket::FREQUENCY_FIXED;
    }

    /**
     * Return the message number of the packet
     *
     * @param string $data
     * @param integer $frequency Message Frequency
     * @return integer Message Number
     */
    private function calculateMessageNumber()
    {
        // If not frequency given caculate
        if($this->frequency == -1) {
            $this->calculateFrequency();
        }

        // Get number according frequency
        switch($this->frequency) {
            // Number 0x??
            case MPacket::FREQUENCY_HIGH :
                $this->messageNumber = ord($this->data[6]);
                break;
            // Number 0xFF??
            case MPacket::FREQUENCY_MEDIUM :
                $this->messageNumber = ord($this->data[7]);
                break;
            // Number 0xFFFF????
            case MPacket::FREQUENCY_LOW :
                $this->messageNumber  = (ord($this->data[8]) & 0xFF) << 8;
                $this->messageNumber += (ord($this->data[9]) & 0xFF);
                break;
            // Number 0xFFFFFF??
            case MPacket::FREQUENCY_FIXED :
                $this->messageNumber = ord($this->data[9]);
                break;
        }
    }

    /**
     * Create a packet with the data
     *
     * @param string $data
     * @return MPacket The Packet
     */
    public static function createPacket($data)
    {
        // Create packet with array
        $chars = str_split($data);

        // Create packet
        $packet = new MPacket(-1,-1,$chars);

        // Return Packet
        return $packet;
    }

    /**
     * Return true if the packet is reliable
     *
     * @return boolean true if the packet is reliable, flase otherwise
     */
    public function isReliable()
    {
        // Get packet flags 
        $flags = ord($this->data[0]);

        // Check if flag is active
        if(($flags & 0x40) != 0) {
            return true;
        }

        // Is no reliable
        return false;
    }

    /**
     * Returns packet number
     *
     * @return integer Packet number
     */
    public function getNumber()
    {
        // Get number 
        $number =   ord($this->data[1]) << 24 |
                    ord($this->data[2]) << 16 |
                    ord($this->data[3]) << 8  |
                    ord($this->data[4]);

        // Return number
        return $number;
    }

    /**
     * Returns the frequency of the packet
     *
     * @return integer
     */
    public function getFrequency()
    {
        return $this->frequency;
    }

    /**
     * Return the message number
     *
     * @return integer
     */
    public function getMessageNumber()
    {
        return $this->messageNumber;
    }

    /**
     * Return current data integer
     *
     * @return integer
     */
    public function getU8()
    {
        return ord($this->data[$this->dataPos++]);
    }

    /**
     * Parse the information inside packet 
     */
    private function parse()
    {
        // Calculate frequency and message number
        $this->calculateFrequency();
        $this->calculateMessageNumber();

        // Data pos is header + extrabytes + messageNumber
        $this->dataPos = 6 + ord($this->data[5]);

        // Calculate data position
        switch ($this->frequency)
        {
            // Fixed and low use 4 bytes
            case MPacket::FREQUENCY_FIXED :
            case MPacket::FREQUENCY_LOW :
                $this->dataPos += 4;
                break;
            // Fixed and low use 2 bytes
            case MPacket::FREQUENCY_MEDIUM :
                $this->dataPos += 2;
                break;
            // Fixed and low use 1 bytes
            case MPacket::FREQUENCY_HIGH :
                $this->dataPos += 1;
                break;
        }
    }
}

?>